package com.gwm.compiler;

import androidx.room.Dao;
import androidx.room.Entity;

import com.google.auto.service.AutoService;
import com.gwm.annotation.layout.IOCWork;
import com.gwm.annotation.room.Autograde;
import com.gwm.annotation.room.Migration;
import com.gwm.annotation.room.RoomDatabaseOpenHelper;
import com.gwm.annotation.room.RoomFactory;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

@AutoService(Processor.class)
public class RoomProcessor extends BaseProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> with = roundEnvironment.getElementsAnnotatedWith(IOCWork.class);
        for (Element ww : with) {
            IOCWork iocWork = ww.getAnnotation(IOCWork.class);
            for (Element elements : roundEnvironment.getElementsAnnotatedWith(RoomDatabaseOpenHelper.class)) {
                if (elements.getKind() == ElementKind.CLASS) {
                    TypeSpec.Builder appDatabaseClass = TypeSpec.classBuilder("AppDatabase");
                    appDatabaseClass.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
                    appDatabaseClass.superclass(ClassName.get("androidx.room", "RoomDatabase"));
                    AnnotationSpec.Builder database = AnnotationSpec.builder(ClassName.get("androidx.room", "Database"));
                    StringBuffer stringBuffer = new StringBuffer();
                    stringBuffer.append("{");
                    Set<? extends Element> eles = roundEnvironment.getElementsAnnotatedWith(Entity.class);
                    for (Element element : eles) {
                        if (element.getKind() == ElementKind.CLASS) {
                            String packageName = processingEnv.getElementUtils().getPackageOf(element).getQualifiedName() + "";
                            String className = element.getSimpleName() + "";

                            stringBuffer.append(packageName);
                            stringBuffer.append(".");

                            stringBuffer.append(className + ".class,\n");
                        }
                    }
                    RoomDatabaseOpenHelper dv = elements.getAnnotation(RoomDatabaseOpenHelper.class);
                    database.addMember("version", dv.version() + "");
                    for (Element element1 : roundEnvironment.getElementsAnnotatedWith(Dao.class)) {
                        if (element1.getKind() == ElementKind.INTERFACE) {
                            String packageName1 = processingEnv.getElementUtils().getPackageOf(element1).getQualifiedName() + "";

                            MethodSpec.Builder getDao = MethodSpec.methodBuilder((element1.getSimpleName() + "").toLowerCase());
                            getDao.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
                            getDao.returns(ClassName.get(packageName1, element1.getSimpleName() + ""));
                            appDatabaseClass.addMethod(getDao.build());

                            MethodSpec.Builder getInstanceDao = MethodSpec.methodBuilder("get" + element1.getSimpleName());
                            getInstanceDao.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
                            getInstanceDao.returns(ClassName.get(packageName1, element1.getSimpleName() + ""));
                            StringBuffer stringBuffer1 = new StringBuffer();
                            stringBuffer1.append("\treturn db." + (element1.getSimpleName() + "").toLowerCase() + "();\n");
                            getInstanceDao.addCode("if(db != null){\n" +
                                    stringBuffer1.toString() +
                                    "}\n" +
                                    "return null;\n");
                            appDatabaseClass.addMethod(getInstanceDao.build());
                        }
                    }
//        stringBuffer.delete(stringBuffer.length(),stringBuffer.length());
                    stringBuffer.append("}");
                    database.addMember("entities", stringBuffer.toString());
                    appDatabaseClass.addAnnotation(database.build());

                    appDatabaseClass.addField(FieldSpec.builder(
                            ClassName.get("com."+iocWork.value()+".db", "AppDatabase"),
                            "db",
                            Modifier.PUBLIC, Modifier.STATIC).build());


                    List<String> migrations = new ArrayList<>();
                    for (Element element1 : roundEnvironment.getElementsAnnotatedWith(Migration.class)) {
                        if (element1.getKind() == ElementKind.FIELD) {
                            Set<Modifier> modifiers = element1.getModifiers();
                            if (modifiers.contains(Modifier.FINAL) && modifiers.contains(Modifier.PUBLIC) && modifiers.contains(Modifier.STATIC)) {
                                String packageName = processingEnv.getElementUtils().getPackageOf(element1).getQualifiedName() + "";
                                String name = element1.getSimpleName() + "";
                                for (Element element2 : roundEnvironment.getElementsAnnotatedWith(RoomDatabaseOpenHelper.class)) {
                                    if (element2.getKind() == ElementKind.CLASS) {
                                        String className = element2.getSimpleName() + "";
                                        migrations.add(".addMigrations(" + packageName + "." + className + "." + name + ")\n");
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    Set<? extends Element> elements11 = roundEnvironment.getElementsAnnotatedWith(Autograde.class);
                    for (Element element : elements11) {
                        if (element.getKind() == ElementKind.CLASS) {
                            List<? extends AnnotationMirror> mirrors = element.getAnnotationMirrors();
                            for (AnnotationMirror mirror : mirrors){
                                if (mirror.toString().contains("Autograde")) {
                                    Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = mirror.getElementValues();
                                    Set<? extends ExecutableElement> keySet = elementValues.keySet();
                                    for (ExecutableElement key : keySet) {
                                        AnnotationValue value = elementValues.get(key);
                                        String[] split = value.toString().split("@com.gwm.annotation.room.Autograde.Version");
                                        for (String string : split){
                                            if (string.contains("(") && string.contains(")")){
                                                string = string.substring(string.indexOf("(") + 1,string.indexOf(")"));
                                                int startVersion = 0;
                                                int endVersion = 0;
                                                String[] split1 = string.split(", ");
                                                for (String s : split1){
                                                    if (s.contains("startVersion")){
                                                        startVersion = Integer.parseInt(s.split("=")[1]);
                                                    }else if (s.contains("endVersion")){
                                                        endVersion = Integer.parseInt(s.split("=")[1]);
                                                    }
                                                }
                                                migrations.add(".addMigrations(new com.jetpackframework.room.DatabaseMigration(" + startVersion + "," + endVersion + "))\n");

                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    Set<? extends Element> factory = roundEnvironment.getElementsAnnotatedWith(RoomFactory.class);
                    for (Element eleFac : factory) {
                        if (eleFac.getKind() == ElementKind.FIELD) {
                            Set<Modifier> modifiers = eleFac.getModifiers();
                            if (modifiers.contains(Modifier.FINAL) && modifiers.contains(Modifier.PUBLIC) && modifiers.contains(Modifier.STATIC)) {
                                String packageName = processingEnv.getElementUtils().getPackageOf(eleFac).getQualifiedName() + "";
                                String name = eleFac.getSimpleName() + "";
                                for (Element element2 : roundEnvironment.getElementsAnnotatedWith(RoomDatabaseOpenHelper.class)) {
                                    if (element2.getKind() == ElementKind.CLASS) {
                                        String className = element2.getSimpleName() + "";
                                        migrations.add(".openHelperFactory(" + packageName + "." + className + "." + name + ")\n");
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    FieldSpec databaseName = FieldSpec.builder(String.class,"name",Modifier.PRIVATE,Modifier.STATIC).build();
                    appDatabaseClass.addField(databaseName);
                    MethodSpec.Builder createDB = MethodSpec.methodBuilder("createDB");
                    createDB.addParameter(ClassName.get("android.content", "Context"), "context");
                    createDB.addParameter(String.class, "databaseName");
                    createDB.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.SYNCHRONIZED);
                    createDB.addCode("name = databaseName; \n");
                    createDB.returns(void.class);
                    StringBuffer code = new StringBuffer();
                    code.append("if(db != null) \n\t return; \n");
                    code.append("db = androidx.room.Room.databaseBuilder(context,AppDatabase.class,databaseName)\n");
                    code.append("\t.allowMainThreadQueries()\n");
                    for (int j = 0; j < migrations.size(); j++) {
                        code.append("\t" + migrations.get(j));
                    }
                    code.append("\t.build();\n");

                    createDB.addCode(code.toString());
                    appDatabaseClass.addMethod(createDB.build());

                    MethodSpec.Builder execSql = MethodSpec.methodBuilder("execSql");
                    execSql.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
                    execSql.addParameter(String.class, "sql");
                    execSql.addCode("db.getOpenHelper().getWritableDatabase().execSQL(sql);\n");
                    appDatabaseClass.addMethod(execSql.build());

                    MethodSpec.Builder execCursor = MethodSpec.methodBuilder("execCursor");
                    execCursor.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
                    execCursor.addParameter(String.class, "sql");
                    execCursor.returns(ClassName.get("android.database", "Cursor"));
                    execCursor.addCode("return db.getOpenHelper().getReadableDatabase().query(sql);\n");
                    appDatabaseClass.addMethod(execCursor.build());

                    MethodSpec.Builder backer = MethodSpec.methodBuilder("backer").addModifiers(Modifier.PUBLIC)
                            .addParameter(String.class,"outPath");
                    backer.addCode("$T.Worker worker = $T.io().createWorker();\n",ClassName.get("com.jetpackframework.rxjetpack.thread","Schedule"),ClassName.get("com.jetpackframework.rxjetpack.thread","Schedules"));
                    backer.addCode("worker.schedule(new Runnable() { \n");
                    backer.addCode("\t@Override \n");
                    backer.addCode("\tpublic void run() { \n");
                    backer.addCode("\t\tdb.close(); \n");
                    backer.addCode("\t\tdb = null; \n");
                    backer.addCode("\t\tString InPath = \"/data/data/\"+ $T.get().getPackageName()+\"/databases\"; \n",ClassName.get("com.jetpackframework","ContextUtil"));
                    backer.addCode("\t\t$T.ZipFolder(InPath,outPath); \n",ClassName.get("com.jetpackframework.room","RoomBackerUtil"));
                    backer.addCode("\t\tcreateDB(ContextUtil.get(),name);\n");
                    backer.addCode("\t} \n});\n");
                    appDatabaseClass.addMethod(backer.build());

                    MethodSpec.Builder store = MethodSpec.methodBuilder("store").addModifiers(Modifier.PUBLIC)
                            .addParameter(String.class,"outPath");
                    store.addCode("$T.Worker worker = $T.io().createWorker();\n",ClassName.get("com.jetpackframework.rxjetpack.thread","Schedule"),ClassName.get("com.jetpackframework.rxjetpack.thread","Schedules"));
                    store.addCode("worker.schedule(new Runnable() { \n");
                    store.addCode("\t@Override \n");
                    store.addCode("\tpublic void run() { \n");
                    store.addCode("\t\tdb.close(); \n");
                    store.addCode("\t\tdb = null; \n");
                    store.addCode("\t\tString InPath = \"/data/data/\"+ $T.get().getPackageName()+\"/databases/\"; \n",ClassName.get("com.jetpackframework","ContextUtil"));
                    store.addCode("\t\t$T.unZipFolder(outPath); \n",ClassName.get("com.jetpackframework.room","RoomBackerUtil"));
                    store.addCode("\t\tcreateDB(ContextUtil.get(),name);\n");
                    store.addCode("\t} \n});\n");
                    appDatabaseClass.addMethod(store.build());
                    writeClazz("com."+iocWork.value()+".db", appDatabaseClass.build());
                }
            }
        }
        return false;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> stringSet = new LinkedHashSet<>();
        stringSet.add(Entity.class.getCanonicalName());
        stringSet.add(Dao.class.getCanonicalName());
        stringSet.add(Migration.class.getCanonicalName());
        stringSet.add(Autograde.class.getCanonicalName());
        stringSet.add(RoomDatabaseOpenHelper.class.getCanonicalName());
        stringSet.add(IOCWork.class.getCanonicalName());
        return stringSet;
    }
}